package biz

import (
	"context"
	"time"

	"github.com/go-kratos/kratos/v2/errors"
	"github.com/go-kratos/kratos/v2/log"
	"gorm.io/gorm"

	pb "gitee.com/danqingzhao/go-dango/api/user/button/v1"
	"gitee.com/danqingzhao/go-dango/pkg/common/core"
	"gitee.com/danqingzhao/go-dango/pkg/proto/settings"
	"gitee.com/danqingzhao/go-dango/pkg/third/orm"
)

type ButtonModel struct {
	Id           uint32            `gorm:"column:id;primary_key;AUTO_INCREMENT;comment:编号" json:"id"`
	CreatedAt    time.Time         `gorm:"column:create_time;comment:创建时间" json:"created_at"`
	UpdatedAt    time.Time         `gorm:"column:update_time;comment:修改时间" json:"updated_at"`
	DeleteAt     gorm.DeletedAt    `gorm:"column:delete_at;type:boolean;comment:是否删除" json:"delete_at"`
	Name         string            `gorm:"column:name;type:varchar(50);not null;uniqueIndex;comment:名称" json:"name"`
	ArrangeOrder uint32            `gorm:"column:arrange_order;type:integer;comment:排序" json:"arrange_order"`
	IsActive     bool              `gorm:"column:is_active;type:boolean;comment:是否激活" json:"is_active"`
	Descr        string            `gorm:"column:descr;type:varchar(254);comment:描述" json:"descr"`
	MenuId       uint32            `gorm:"column:menu_id;foreignKey:MenuId;references:Id;not null;constraint:OnDelete:CASCADE;comment:菜单" json:"menu"`
	Menu         MenuModel         `gorm:"foreignKey:MenuId;constraint:OnDelete:CASCADE"`
	Permissions  []PermissionModel `gorm:"many2many:auth_button_permission;joinForeignKey:button_id;joinReferences:permission_id;constraint:OnDelete:CASCADE"`
}

func (m *ButtonModel) TableName() string {
	return "user_button"
}

type ButtonRepo interface {
	CreateModel(context.Context, *ButtonModel) error
	UpdateModel(context.Context, ButtonModel, map[string]any, ...any) error
	DeleteModel(context.Context, ...any) error
	FindModel(context.Context, []string, ...any) (*ButtonModel, error)
	ListModel(context.Context, orm.QueryParams) (int64, []ButtonModel, error)
	ListPolicies(context.Context, ButtonModel) ([][]string, [][]string)
}

type ButtonUsecase struct {
	log        *log.Helper
	permRepo   PermissionRepo
	menuRepo   MenuRepo
	buttonRepo ButtonRepo
	policyRepo PolicyRepo
}

func NewButtonUsecase(
	logHelpers *settings.LogHelpers,
	permRepo PermissionRepo,
	menuRepo MenuRepo,
	buttonRepo ButtonRepo,
	policyRepo PolicyRepo,
) *ButtonUsecase {
	return &ButtonUsecase{
		log:        logHelpers.Biz,
		permRepo:   permRepo,
		menuRepo:   menuRepo,
		buttonRepo: buttonRepo,
		policyRepo: policyRepo,
	}
}

func (uc *ButtonUsecase) association(
	ctx context.Context,
	m ButtonModel,
	menuId uint32,
	permIds []uint32,
) (*ButtonModel, *errors.Error) {
	menu, fErr := uc.menuRepo.FindModel(ctx, nil, menuId)
	if fErr != nil {
		rErr := core.NewGormError(fErr, pb.ErrorReason_BUTTON_FIND_MENU_ERROR.String(), core.GenerateTempDataByID(menuId))
		uc.log.WithContext(ctx).Error(rErr.Error())
		return nil, rErr
	}
	m.MenuId = menu.Id
	m.Menu = *menu
	qp := orm.QueryParams{
		Preloads: []string{},
		Query:    map[string]any{"id in ?": permIds},
		OrderBy:  []string{"id"},
		IsCount:  false,
		Limit:    0,
		Offset:   0,
	}
	_, pms, lErr := uc.permRepo.ListModel(ctx, qp)
	if lErr != nil {
		rErr := core.NewGormError(lErr, pb.ErrorReason_BUTTON_LIST_PERMISSION_ERROR.String(), nil)
		uc.log.WithContext(ctx).Error(rErr.Error())
		return nil, rErr
	}
	if len(pms) > 0 {
		m.Permissions = pms
	}
	return &m, nil
}

func (uc *ButtonUsecase) CreateButton(
	ctx context.Context,
	menuId uint32,
	permIds []uint32,
	m ButtonModel,
) (*ButtonModel, *errors.Error) {
	am, aErr := uc.association(ctx, m, menuId, permIds)
	if aErr != nil {
		return nil, aErr
	}
	if err := uc.buttonRepo.CreateModel(ctx, am); err != nil {
		rErr := core.NewGormError(err, pb.ErrorReason_BUTTON_CREATE_FAILED.String(), nil)
		uc.log.WithContext(ctx).Error(rErr.Error())
		return nil, rErr
	}
	pRules, gRules := uc.buttonRepo.ListPolicies(ctx, *am)
	if err := uc.policyRepo.AddPolicies(ctx, pRules, gRules); err != nil {
		rErr := errors.InternalServer(pb.ErrorReason_BUTTON_ADD_POLICY_ERROR.String(), err.Error()).WithCause(err)
		uc.log.WithContext(ctx).Error(rErr.Error())
		return nil, rErr
	}
	return am, nil
}

func (uc *ButtonUsecase) UpdateButtonById(
	ctx context.Context,
	buttonId uint32,
	menuId uint32,
	permIds []uint32,
	im ButtonModel,
) (*ButtonModel, *errors.Error) {
	am, aErr := uc.association(ctx, im, menuId, permIds)
	if aErr != nil {
		return nil, aErr
	}
	ummap := make(map[string]any)
	if am.Permissions != nil {
		ummap["Permissions"] = am.Permissions
	}
	m, fErr := uc.FindButtonByID(ctx, []string{"Permissions", "Menu"}, buttonId)
	if fErr != nil {
		return nil, fErr
	}
	pOldRules, gOldRules := uc.buttonRepo.ListPolicies(ctx, *m)
	if err := uc.buttonRepo.UpdateModel(ctx, *am, ummap, "id = ?", buttonId); err != nil {
		rErr := core.NewGormError(err, pb.ErrorReason_BUTTON_UPDATE_FAILED.String(), core.GenerateTempDataByID(buttonId))
		uc.log.WithContext(ctx).Error(rErr.Error())
		return nil, rErr
	}
	pNewRules, gNewRules := uc.buttonRepo.ListPolicies(ctx, *am)
	if err := uc.policyRepo.RemovePolicies(ctx, pOldRules, gOldRules); err != nil {
		rErr := errors.InternalServer(pb.ErrorReason_BUTTON_REMOVE_POLICY_ERROR.String(), err.Error()).WithCause(err)
		uc.log.WithContext(ctx).Error(rErr.Error())
		return nil, rErr
	}
	if err := uc.policyRepo.AddPolicies(ctx, pNewRules, gNewRules); err != nil {
		rErr := errors.InternalServer(pb.ErrorReason_BUTTON_ADD_POLICY_ERROR.String(), err.Error()).WithCause(err)
		uc.log.WithContext(ctx).Error(rErr.Error())
		return nil, rErr
	}
	return uc.FindButtonByID(ctx, []string{"Permissions", "Menu"}, buttonId)
}

func (uc *ButtonUsecase) DeleteButtonById(ctx context.Context, buttonId uint32) *errors.Error {
	m, fErr := uc.FindButtonByID(ctx, []string{"Permissions", "Menu"}, buttonId)
	if fErr != nil {
		return fErr
	}
	pRules, gRules := uc.buttonRepo.ListPolicies(ctx, *m)
	if err := uc.menuRepo.DeleteModel(ctx, buttonId); err != nil {
		rErr := core.NewGormError(err, pb.ErrorReason_BUTTON_DELETE_FAILED.String(), core.GenerateTempDataByID(buttonId))
		uc.log.WithContext(ctx).Error(rErr.Error())
		return rErr
	}
	if err := uc.policyRepo.RemovePolicies(ctx, pRules, gRules); err != nil {
		rErr := errors.InternalServer(pb.ErrorReason_BUTTON_REMOVE_POLICY_ERROR.String(), err.Error()).WithCause(err)
		uc.log.WithContext(ctx).Error(rErr.Error())
		return rErr
	}
	return nil
}

func (uc *ButtonUsecase) FindButtonByID(
	ctx context.Context,
	preloads []string,
	buttonId uint32,
) (*ButtonModel, *errors.Error) {
	m, err := uc.buttonRepo.FindModel(ctx, preloads, buttonId)
	if err != nil {
		rErr := core.NewGormError(err, pb.ErrorReason_BUTTON_FIND_FAILED.String(), core.GenerateTempDataByID(buttonId))
		uc.log.WithContext(ctx).Error(rErr.Error())
		return nil, rErr
	}
	return m, nil
}

func (uc *ButtonUsecase) ListButton(
	ctx context.Context,
	preloads []string,
	page, size int,
	query map[string]any,
) (int64, []ButtonModel, *errors.Error) {
	qp := orm.QueryParams{
		Preloads: []string{},
		Query:    query,
		OrderBy:  []string{"id"},
		Limit:    max(size, 0),
		Offset:   max(page-1, 0),
		IsCount:  true,
	}
	count, ms, err := uc.buttonRepo.ListModel(ctx, qp)
	if err != nil {
		rErr := core.NewGormError(err, pb.ErrorReason_BUTTON_LIST_FAILED.String(), nil)
		uc.log.WithContext(ctx).Error(rErr.Error())
		return 0, nil, rErr
	}
	return count, ms, nil
}
